package utils

import (
	"ChdChat-Gin/initialize"
	"ChdChat-Gin/model"
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/golang-jwt/jwt/v4"
	"github.com/gomodule/redigo/redis"
	"io"
	"log"
	"regexp"
	"strings"
	"time"
)

var (
	AccountFormatErr  = errors.New("account must be between 4 to 16 letters or digits")
	PasswordFormatErr = errors.New("password must be between 6 and 16 letters or digits")
	UserTypeFormatErr = errors.New("user type must be 0(administrator) or 1(normal)")

	InvalidTokenErr = errors.New("invalid token")
)

func VerifyRegisterBody(registerBody model.UserAccounts) error {
	acc := regexp.MustCompile(`^[A-Za-z0-9]{4,16}$`)
	pas := regexp.MustCompile(`^[A-Za-z0-9]{6,16}$`)
	if !acc.MatchString(registerBody.Account) {
		return AccountFormatErr
	}
	if !pas.MatchString(registerBody.Password) {
		return PasswordFormatErr
	}
	if registerBody.Type != 0|1 {
		return UserTypeFormatErr
	}
	return nil
}

// GenerateTokenUsingHs256 生成token
func GenerateTokenUsingHs256(user model.Signature) (string, error) {
	currentTime := time.Now()
	claims := &model.Claims{
		Account: user.Account,
		Name:    user.Name,
		Type:    user.Type,
		RdsKey:  user.RdsKey,
		AesKey:  user.AesKey,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "chd",
			ExpiresAt: jwt.NewNumericDate(currentTime.Add(time.Hour)),
			IssuedAt:  jwt.NewNumericDate(currentTime),
		},
	}
	tokenStr, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(model.JwtKey)
	if err != nil {
		return "", fmt.Errorf("jwt.NewWithClaims:%v", err)
	}
	return tokenStr, nil
}

// ParseToken 解析token
func ParseToken(tokenStr string) (*model.Claims, error) {
	splitToken := strings.Trim(tokenStr, "\"")
	token, err := jwt.ParseWithClaims(splitToken, &model.Claims{}, func(token *jwt.Token) (interface{}, error) {
		return model.JwtKey, nil
	})
	if err != nil {
		log.Printf("jwt.ParseWithClaims:%v\n", err)
		if err, ok := err.(*jwt.ValidationError); ok {
			if err.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, InvalidTokenErr
			}
			if err.Errors&jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet != 0 {
				return nil, InvalidTokenErr
			}
		}
		return nil, errors.New("unknown error")
	}
	if claims, ok := token.Claims.(*model.Claims); ok && token.Valid {
		return claims, nil
	} else {
		return nil, InvalidTokenErr
	}
}

// EncryptUserID AES加密用户ID
func EncryptUserID(userID string) (string, string, error) {
	aesKey := make([]byte, 16)
	if _, err := rand.Read(aesKey); err != nil {
		return "", "", fmt.Errorf("rand.Read:%v", err)
	}
	plaintext := []byte(userID)
	// 创建 AES 加密块
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return "", "", fmt.Errorf("aes.NewCipher:%v", err)
	}

	ciphertext := make([]byte, 2*block.BlockSize())
	// 选择加密模式和填充方式
	iv := ciphertext[:aes.BlockSize]
	if _, err = io.ReadFull(rand.Reader, iv); err != nil {
		return "", "", fmt.Errorf("io.ReadFull:%v", err)
	}
	// 使用填充方式（PKCS7）将明文填充到正确的长度
	padding := aes.BlockSize - len(plaintext)%aes.BlockSize
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	paddedPlaintext := append(plaintext, padText...)

	// 执行加密操作
	encryption := cipher.NewCBCEncrypter(block, iv)
	encryption.CryptBlocks(ciphertext[aes.BlockSize:], paddedPlaintext)
	// 对加密后的结果进行 Base64 编码，方便存储和传输
	encoded := base64.StdEncoding.EncodeToString(ciphertext)

	// 密钥转化为十六进制
	return encoded, hex.EncodeToString(aesKey), nil
}

// SaveEncryptedData 保存加密后的用户ID及用户信息
func SaveEncryptedData(userInfo model.UserInfos, encryptedData string) (string, error) {
	conn := initialize.RedisPool.Get()
	defer func(conn redis.Conn) {
		err := conn.Close()
		if err != nil {
			panic(err)
		}
	}(conn)

	// 使用用户ID+随机字符串作为 redis key
	temp := make([]byte, 4)
	if _, err := rand.Read(temp); err != nil {
		return "", fmt.Errorf("rand.Read:%v", err)
	}
	rdsKey := fmt.Sprintf("%s-%s", userInfo.Account, hex.EncodeToString(temp))

	rdsVal := struct {
		Encoded  string          `json:"encoded"`
		UserInfo model.UserInfos `json:"userInfo"`
	}{
		Encoded:  encryptedData,
		UserInfo: userInfo,
	}
	// 将认证信息和用户信息编码转换为字符串形式保存
	marshaled, err := json.Marshal(rdsVal)
	if err != nil {
		return "", fmt.Errorf("json.Marshal:%v", err)
	}
	rdsString := string(marshaled)

	// 保存的认证信息设置过期时间
	if _, err = conn.Do("SET", rdsKey, rdsString, "EX", int(time.Hour.Seconds())); err != nil {
		return "", fmt.Errorf("redisConn.Do:%v", err)
	}

	return rdsKey, nil
}
